using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SensorServices.Rfid;
using Microsoft.SensorServices.Rfid.Utilities;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text.RegularExpressions;

namespace CodePlex.EventHandlers.FilterEventHandler
{
    public class FilterEventHandler : RfidEventHandlerBase
    {
        #region Private Variables

        static RfidEventHandlerMetadata s_filterMetadata;
        static readonly string StartsWith = "StartsWith";
        static readonly string EndsWith = "EndsWith";
        static readonly string Contains = "Contains";
        static readonly string Operator = "Operator Is And";
        static readonly string PropertyString = "Property";
        static readonly string VendorKey = "VendorKey";
        static readonly string IgnoreCase = "IgnoreCase";
        static readonly string RegEx = "RegEx";
        static readonly string MatchRegExOnly = "MatchRegExOnly";

        enum Members
        {
            TagID,
            AntennaName,
            DeviceName,
            TagData,
            NSI,
            VendorSpecificData
        }

        string m_StartsWithString;
        string m_EndsWithString;
        string m_ContainsString;
        string m_VendorKey;
        bool m_Operator;
        bool m_IgnoreCase;
        bool m_matchRegExOnly;
        StringComparison m_stringComparision;
        Members m_Property;
        Regex m_regex;
        ILogger m_logger;

        #endregion

        #region EH Required functions

        public static RfidEventHandlerMetadata GetEventHandlerMetadata(bool getVendorExtensionsAlso)
        {
            if (s_filterMetadata == null)
            {
                Dictionary<string, RfidEventHandlerParameterMetadata> parmMetadata = new Dictionary<string,RfidEventHandlerParameterMetadata>();

                parmMetadata.Add(StartsWith, new RfidEventHandlerParameterMetadata(typeof(string), "Starts With String to check with", String.Empty, false));
                parmMetadata.Add(EndsWith, new RfidEventHandlerParameterMetadata(typeof(string), "Ends With String to check with", String.Empty, false));
                parmMetadata.Add(Contains, new RfidEventHandlerParameterMetadata(typeof(string), "String to check Tag Contains", String.Empty, false));
                parmMetadata.Add(Operator, new RfidEventHandlerParameterMetadata(typeof(bool), "If true use AND else OR", true, false));
                parmMetadata.Add(PropertyString, new RfidEventHandlerParameterMetadata(typeof(string), "The Property to filter based on", Members.TagID.ToString(), false, GetValueSetFromEnum()));
                parmMetadata.Add(VendorKey, new RfidEventHandlerParameterMetadata(typeof(string), "The vendor key in the Vendor Dictionary of Tag to filter on", String.Empty, false));
                parmMetadata.Add(IgnoreCase, new RfidEventHandlerParameterMetadata(typeof(bool), "Ignore case while filtering", true, false));
                parmMetadata.Add(RegEx, new RfidEventHandlerParameterMetadata(typeof(string), "The Regular Expression to match against",String.Empty, false));
                parmMetadata.Add(MatchRegExOnly, new RfidEventHandlerParameterMetadata(typeof(bool), "Match only against RegEx. Ignore all other filter settings.", false, false));

                s_filterMetadata = new RfidEventHandlerMetadata("Filter Event Handler", parmMetadata);
            }
            return s_filterMetadata;
        }

        public override void Init(Dictionary<string, object> parameters, RfidProcessContext container)
        {
            m_logger = RfidProcessContext.GetLogger("FilterEventHandler");
            m_logger.Info("Entering Init");

            m_ContainsString = GetParameter<string>(parameters, Contains);
            m_EndsWithString = GetParameter<string>(parameters, EndsWith);
            m_StartsWithString = GetParameter<string>(parameters, StartsWith);
            m_Operator = GetParameter<bool>(parameters, Operator);
            m_Property = GetParameter<Members>(parameters, PropertyString);
            m_matchRegExOnly = GetParameter<bool>(parameters, MatchRegExOnly);
            string regexString = GetParameter<string>(parameters, RegEx);

            if (m_matchRegExOnly && String.IsNullOrEmpty(regexString))
                throw new ArgumentException("If MatchOnlyRegEx is set, the RexEx value should be non-empty.");

            m_IgnoreCase = GetParameter<bool>(parameters, IgnoreCase);

            m_stringComparision = (m_IgnoreCase) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

            if (m_Property == Members.VendorSpecificData)
            {
                m_VendorKey = GetParameter<string>(parameters, VendorKey);
                if (String.IsNullOrEmpty(m_VendorKey))
                {
                    throw new ArgumentException("If Property is VendorSpecificData, VendorKey should be a non-null non-empty string.");
                }
            }

            if (!String.IsNullOrEmpty(regexString))
            {
                if (m_IgnoreCase)
                    m_regex = new Regex(regexString, RegexOptions.IgnoreCase);
                else m_regex = new Regex(regexString);
            }
        }
                        
        [RfidEventHandlerMethod]
        public TagReadEvent HandleTagReadEvent(TagReadEvent tagReadEvent)
        {
            bool ret;
            
            string input = GetStringToFilter(tagReadEvent);

            if (String.IsNullOrEmpty(input))
                return null;


            bool isRegExMatch = CheckWithRegEx(input);
            
            if (m_matchRegExOnly)
            {
                ret = isRegExMatch;
            }
            else
            {
                bool isStartsWith = CheckStartsWith(input);
                bool isEndsWith = CheckEndsWith(input);
                bool isContains = CheckContains(input);


                if (m_Operator)
                {
                    ret = isStartsWith && isContains && isEndsWith && isRegExMatch;
                }
                else
                {
                    ret = isStartsWith || isContains || isEndsWith || isRegExMatch;
                }
            }

            if (ret)
                return tagReadEvent;

            return null;
        }

        #endregion

        #region Filtering Functions

        private bool CheckWithRegEx(string input)
        {
            if (m_regex == null)
                return m_Operator;

            return m_regex.IsMatch(input);
        }        

        private bool CheckStartsWith(string tagID)
        {
            if (!String.IsNullOrEmpty(m_StartsWithString))
            {
                return tagID.StartsWith(m_StartsWithString, m_stringComparision);
            }

            return m_Operator;
        }

        private bool CheckEndsWith(string tagID)
        {
            if (!String.IsNullOrEmpty(m_EndsWithString))
            {
                return tagID.EndsWith(m_EndsWithString, m_stringComparision);
            }

            return m_Operator;
        }

        private bool CheckContains(string tagID)
        {
            if (!String.IsNullOrEmpty(m_ContainsString))
            {
                return tagID.Contains(m_ContainsString);
            }

            return m_Operator;
        }

        #endregion

        private string GetStringToFilter(TagReadEvent tagReadEvent)
        {
            string retval;

            switch (m_Property)
            {
                case Members.AntennaName: { retval = tagReadEvent.Source; break; }
                case Members.DeviceName: { retval = tagReadEvent.DeviceName; break; }
                case Members.NSI: { retval = GetToString(tagReadEvent.GetNumberingSystemIdentifier()); break; }
                case Members.TagData: { retval = GetToString(tagReadEvent.GetData()); break; }
                case Members.TagID: { retval = GetToString(tagReadEvent.GetId()); break; }
                case Members.VendorSpecificData:
                    {
                        object val;
                        if (tagReadEvent.VendorSpecificData.TryGetValue(m_VendorKey, out val))
                        { retval = GetToString(val); }
                        retval = null;
                        break;
                    }
                default: { retval = null; break; }
            }
            return retval;
        }

        private T GetParameter<T>(Dictionary<string, object> parameters, string key)
        {
            T t = default(T);
            object val;

            if (parameters.TryGetValue(key, out val))
            {
                m_logger.Verbose("Parameters contain key {0} with value {1}", key, val);
                if (val.GetType().Equals(typeof(T)))
                {
                    t = (T)val;
                }
                else if (typeof(T).IsEnum && val.GetType().Equals(typeof(string)))
                {
                    t = (T)Enum.Parse(typeof(T), (string)val, true);
                }
            }
            return t;
        }

        private static string GetToString(object value)
        {
            if (value == null)
                return null;

            if (value.GetType().Equals(typeof(byte[])))
            {
                return HexUtilities.HexEncode((byte[])value);
            }

            return value.ToString();
        }

        private static Collection<object> GetValueSetFromEnum()
        {
            Collection<object> valueSet = new Collection<object>();
            string[] names = Enum.GetNames(typeof(Members));

            foreach (string name in names)
            {
                valueSet.Add(name);
            }
            return valueSet;
        }        
    }
}
